/**
 * @author:albert
 */

package com.bkbw.live.battle.business;

import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import javassist.expr.Instanceof;

import com.bkbw.live.battle.map.MapAStarInfo;
import com.bkbw.live.battle.map.MapManager;

import org.apache.commons.math3.geometry.euclidean.threed.Vector3D;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.badlogic.gdx.ai.msg.MessageDispatcher;
import com.badlogic.gdx.math.MathUtils;
import com.bkbw.live.common.GameConfig;
import com.bkbw.live.common.dataproto.ClientToBattleServerPb.*;
import com.bkbw.live.common.dataproto.CommonMessagePb.Vector3IntPb;
import com.bkbw.live.common.dataproto.EnumConfigPb.*;
import com.bkbw.live.battle.actor.BattleActor;
import com.bkbw.live.battle.bean.PlayerInDungeon;
import com.bkbw.live.battle.bean.UserAgent;
import com.bkbw.live.battle.business.GameObject.GameObjectGroup;
import com.bkbw.live.battle.business.UnitObject.ControlType;
import com.bkbw.live.battle.database.DatabaseManager;
import com.bkbw.live.battle.database.bean.RoleBean;
import com.bkbw.live.battle.database.bean.WeaponsBean;

import scala.collection.generic.BitOperations.Int;

import com.bkbw.live.battle.util.Vector3Helper;

public class GameLogicMap {
	
	private static final int UserAttack = 20;
	private static final int TowerAttack = 1;
	
	Logger logger = LoggerFactory.getLogger(GameConfig.LoggerName);
	protected int objectId = 10000;
	//protected HashMap<Integer, GameObject> gameObjects = new HashMap<Integer, GameObject>();
	protected GameHashMap<GameObject> gameObjects = new GameHashMap<GameObject>();
	//protected LinkedList<GameObject> toAddList = new LinkedList<GameObject>();
	//protected LinkedList<GameObject> toRemoveList = new LinkedList<GameObject>();
	
	protected MapAStarInfo astarInfo = MapManager.getInstance().getMapInfoById("");
	public MapAStarInfo getAstarInfo() {
		return astarInfo;
	}
	
	protected MessageDispatcher messageDispatcher = new MessageDispatcher();
	public MessageDispatcher getMessageDispatcher() {
		return messageDispatcher;
	}
	
	BattleActor actor;
	
	public GameLogicMap(BattleActor actor)
	{
		this.actor = actor;
		Vector3IntPb.Builder builder = Vector3IntPb.newBuilder();
		builder.setX(32);
		builder.setZ(32);
		// hide build temp
		//BuildingObject build = createBuildingByUser(0, 2, builder.build());
		//build.setGroup(GameObjectGroup.UserGroup1);
	}
	
	// ------------------ call between table actor ------------------
	
	public void attackNotify(int id, int targetId)
	{
		UnitObject unit = (UnitObject)gameObjects.get(id);
		UnitObject target = (UnitObject)gameObjects.get(targetId);
		actor.attackNotify(unit.getId(), target.getId());
	}
	
	public void damageNotify(int id, int targetId, int damage, int currentHp)
	{
		UnitObject unit = (UnitObject)gameObjects.get(id);
		UnitObject target = (UnitObject)gameObjects.get(targetId);
		actor.damageNotify(unit.getId(), target.getId(), damage, currentHp);
	}
	
	public void bulletCreateNotify(int id, Vector3D pos, Vector3D head, float speed)
	{
		actor.bulletCreateNotify(id, Vector3Helper.convertToDataprotoVector3(pos),Vector3Helper.convertToDataprotoVector3(head), speed);
		logger.debug("bullet "+id+" create");
	}
	
	public void bulletMoveNotify(int id, Vector3D pos, Vector3D head, float speed) 
	{
		actor.bulletMoveNotify(id, Vector3Helper.convertToDataprotoVector3(pos),Vector3Helper.convertToDataprotoVector3(head), speed);
		logger.debug("bullet "+id+" move "+pos);
	}
	
	public void bulletRemoveNotify(int id)
	{
		actor.bulletRemoveNotify(id);
		logger.debug("bullet "+id+" remove");
	}
	
	public void setUnitAttackTarget(int unitId, int attackId) {
		UnitObject unit = getUnitObjectById(unitId);
		UnitObject attack = getUnitObjectById(attackId);
		if (unit != null && attack != null) {
			unit.setTargetEnemy(attack);
		}
	}
	static float spellTime = 0.35f;
	public void createPlayerByUser(UserAgent agent)
	{
		UnitObject go = new UnitObject(getIncrementId(), this);
		go.setAgent(agent);
		go.setRoleId(1);
		//go.init(ControlType.Manual);
		RoleBean role = DatabaseManager.getInstance().getRoleTable().getBeanByKey(go.getRoleId());
		WeaponsBean weapon = DatabaseManager.getInstance().getWeaponsTable().getBeanByKey(role.getWeapon());
		go.init(ControlType.Manual, role, weapon);
		
		//int uid = agent.getUid();
		int uid = go.getRoleId();
		if (uid == 1) {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 1.5f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("missileSpeed", 5.0f);
			param.put("moveMode", BulletMoveMode.Follow);
			param.put("collisionMode", BulletCollisionMode.Enemy);
			param.put("distance", 10.0f);
			param.put("distance", weapon.getAttackRange());
			go.bindAbility("ShootAbility", "dahan", param);
			
		}
		else if (uid == 1001) {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 1.0f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("distance", 2.0f);
			go.bindAbility("SlashAbility", "knight", param);
		}
		else {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 0.5f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("missileSpeed", 5.0f);
			param.put("moveMode", BulletMoveMode.Follow);
			param.put("collisionMode", BulletCollisionMode.Any);
			param.put("distance", 5.0f);
			go.bindAbility("ShootAbility", "gunBoy", param);
		}
		//toAddList.add(go);
		//gameObjects.prepareToAdd(go);
		
		go.setFinalAttack(UserAttack);
		gameObjects.put(go.getId(), go);
		go.setGroup(GameObjectGroup.UserGroup1);
		agent.setUnit(go);
		logger.debug("createplayer charid "+agent.getCharId()+" id "+go.getId());
	}
	
	public void createRobot()
	{
		UnitObject go = new UnitObject(getIncrementId(), this);
		//go.init(ControlType.Monster);
		//int uid = MathUtils.random(0, 9999);
		go.setRoleId(1001);
		//int uid = agent.getUid();
		
		RoleBean role = DatabaseManager.getInstance().getRoleTable().getBeanByKey(go.getRoleId());
		WeaponsBean weapon = DatabaseManager.getInstance().getWeaponsTable().getBeanByKey(role.getWeapon());
		go.init(ControlType.Monster, role, weapon);
		int uid = go.getRoleId();
		if (uid == 1) {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 1.0f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("missileSpeed", 10.0f);
			param.put("moveMode", BulletMoveMode.Follow);
			param.put("collisionMode", BulletCollisionMode.Any);
			param.put("distance", 5.0f);
			go.bindAbility("ShootAbility", "dahan", param);
			
		}
		else if (uid == 1001) {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 1.0f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			//param.put("distance", 2.0f);
			param.put("distance", weapon.getAttackRange());
			go.bindAbility("SlashAbility", "knight", param);
		}
		else {
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 0.5f);
			param.put("spellTime", spellTime);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("missileSpeed", 5.0f);
			param.put("moveMode", BulletMoveMode.Follow);
			param.put("collisionMode", BulletCollisionMode.Any);
			param.put("distance", 5.0f);
			go.bindAbility("ShootAbility", "gunBoy", param);
		}
		float startx = 0;
		float startz = 0;
		switch (go.getId() % 4) {
		case 0:
			startx = 3;
			startz = 3;
			break;
		case 1:
			startx = 61;
			startz = 3;
			break;
		case 2:
			startx = 61;
			startz = 61;
			break;
		case 3:
			startx = 3;
			startz = 61;
			break;
		}
		go.setPosition(new Vector3D(startx, 0, startz));
		
		go.setGroup(GameObjectGroup.Monster);
		actor.broadcastMonsterEnter(go);
		gameObjects.prepareToAdd(go);
	}
	
	public BulletObject createBullet()
	{
		BulletObject bulletObject = new BulletObject(getIncrementId(), this);
		//toAddList.add(bulletObject);
		//gameObjects.put(bulletObject.getId(), bulletObject);
		gameObjects.prepareToAdd(bulletObject);
		return bulletObject;
	}
	
	public void removeBullet(BulletObject bullet) {
		gameObjects.prepareToRemove(bullet);
		bulletRemoveNotify(bullet.getId());
		//bullet.setNeedRemove(true);
	}
	
	public boolean moveBuild(int buildGameId, Vector3IntPb newpos)
	{
		BuildingObject building = null;
		if (gameObjects.containsKey(buildGameId)) {
			if  (!(gameObjects.get(buildGameId) instanceof BuildingObject)) {
				return false;
			}
			// 先把该建筑的障碍清空，重新检验是否能摆放
			building = (BuildingObject)gameObjects.get(buildGameId);
			int data[] = BuildingConf.getInstance().queryItemData(building.getBuildId());
			Vector3IntPb buildingPos = building.getBuildingPos();
			for (int i = 0; i < data.length / 2; i++) {
				astarInfo.setBarrierPoint(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ(), 0);
			}
			for (int i = 0; i < data.length / 2; i++) {
				if (!astarInfo.canCross(data[i * 2] + newpos.getX(), data[i * 2 + 1] + newpos.getZ())) {
					return false;
				}
			}
			for (int i = 0; i < data.length / 2; i++) {
				//logger.debug("build at " + data[i * 2] + newpos.getX() + " " + data[i * 2 + 1] + newpos.getZ());
				astarInfo.setBarrierPoint(data[i * 2] + newpos.getX(), data[i * 2 + 1] + newpos.getZ(), 1);
			}
			return true;
		}	
		else {
			return false;
		}
	}

	public BuildingObject createBuildingByUser(int creatorId, int buildId, Vector3IntPb buildingPos)
	{
		logger.debug("build pos" + buildingPos.getX() + " " + buildingPos.getZ());
		int data[] = BuildingConf.getInstance().queryItemData(buildId);
		for (int i = 0; i < data.length / 2; i++) {
			if (!astarInfo.canCross(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ())) {
				return null;
			}
		}
		for (int i = 0; i < data.length / 2; i++) {
			//logger.debug("build at " + data[i * 2] + buildingPos.getX() + " " + data[i * 2 + 1] + buildingPos.getZ());
			astarInfo.setBarrierPoint(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ(), 1);
		}
		BuildingObject building = new BuildingObject(getIncrementId(), this);
		building.init(buildId, buildingPos);
		if (building.getBuildId() == 1) {
			building.setRoleId(1);
			RoleBean role = DatabaseManager.getInstance().getRoleTable().getBeanByKey(building.getRoleId());
			WeaponsBean weapon = DatabaseManager.getInstance().getWeaponsTable().getBeanByKey(role.getWeapon());
			building.init(ControlType.Building, role, weapon);
			//building.init(ControlType.Building);
			//building.setFinalHp(10);
			HashMap<String, Object> param = new HashMap<String, Object>();
			param.put("cooldown", 1.0f);
			param.put("spellTime", 0.5f);
			param.put("abilityType", AbilityType.NormalAttack);
			param.put("missileSpeed", 10.0f);
			//param.put("moveMode", BulletMoveMode.Follow);
			//param.put("collisionMode", BulletCollisionMode.Enemy);
			param.put("moveMode", BulletMoveMode.Curve);
			param.put("collisionMode", BulletCollisionMode.TargetPos);
			//param.put("distance", 5.0f);
			param.put("distance", weapon.getAttackRange());
			building.bindAbility("ShootAbility", "dahan", param);
			building.setFinalAttack(TowerAttack);
			building.setFinalHp(1000);
			building.setCurrentHp(1000);
			//building
		}
		building.setGroup(GameObjectGroup.UserGroup1);
		gameObjects.put(building.getId(), building);
		
		return building;
	}
	
	public boolean removeBuildingByAi(BuildingObject building)
	{
		if (!gameObjects.containsKey(building.getId())) {
			return false;
		}
		int data[] = BuildingConf.getInstance().queryItemData(building.getBuildId());
		Vector3IntPb buildingPos = building.getBuildingPos();
		for (int i = 0; i < data.length / 2; i++) {
			astarInfo.setBarrierPoint(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ(), 0);
		}
		gameObjects.prepareToRemove(building);
		return true;
	}
	
	public boolean removeBuildingByUser(BuildingObject building)
	{
		if (!gameObjects.containsKey(building.getId())) {
			return false;
		}
		int data[] = BuildingConf.getInstance().queryItemData(building.getBuildId());
		Vector3IntPb buildingPos = building.getBuildingPos();
		for (int i = 0; i < data.length / 2; i++) {
			astarInfo.setBarrierPoint(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ(), 0);
		}
		gameObjects.remove(building);
		return true;
	}
	
	public boolean removeBuildingByUser(int buildingId)
	{
		BuildingObject building = null;
		if (gameObjects.containsKey(buildingId)) {
			if  (!(gameObjects.get(buildingId) instanceof BuildingObject)) {
				return false;
			}
			building = (BuildingObject)gameObjects.get(buildingId);
			int data[] = BuildingConf.getInstance().queryItemData(building.getBuildId());
			Vector3IntPb buildingPos = building.getBuildingPos();
			for (int i = 0; i < data.length / 2; i++) {
				astarInfo.setBarrierPoint(data[i * 2] + buildingPos.getX(), data[i * 2 + 1] + buildingPos.getZ(), 0);
			}
			gameObjects.remove(building);
			return true;
		}	
		else {
			return false;
		}
	}
	
	public void buildExplode(int buildId) {
		if (gameObjects.containsKey(buildId)) {
			
			GameObject gameObject = gameObjects.get(buildId);
			if (!(gameObject instanceof BuildingObject)) {
				return;
			}
			BuildingObject buildingObject = (BuildingObject)gameObject;
			removeBuildingByAi(buildingObject);
			actor.mapBuildRemove(buildId);
		}
	}
	
	public LinkedList<BuildingObject> getBuildingList() {
		LinkedList<BuildingObject> list = new LinkedList<BuildingObject>();
		for (GameObject object : gameObjects.values()) {
			if (object instanceof BuildingObject) {
				list.add((BuildingObject)object);
			}
		}
		return list;
	}
	
	public int getIncrementId()
	{
		return this.objectId++;
	}
	
	public void unitMoveStateChange(int unitId, int move) 
	{
		this.actor.unitMoveStateChange(unitId, move);
	}
	
	// ---------------------------- map api ---------------
	
	public boolean canUnitCross(float x, float z)
	{
		int xint = Math.round(x);
		int zint = Math.round(z);
		if (xint >= 0 && xint < astarInfo.getMaxX()) {
			if (zint >= 0 && zint < astarInfo.getMaxZ()) {
				return astarInfo.canCross(Math.round(x), Math.round(z));
			}
		}
		return false;
	}
	
	float spawnMonster = 0;
	int counter = 0;
	public void update(float dt)
	{
//		spawnMonster += dt;
//		if (spawnMonster > 3000) {
//			spawnMonster = 0;
//			createRobot();
//		}
		if (counter % 300 == 0) {
		//if (counter == 300) {
			if (counter < 6000) {
			//	createRobot();
			}
			createRobot();
		}
		counter++;

		for (GameObject gameObject : gameObjects.values()) {
			gameObject.update(dt);
		}
		
		gameObjects.commit();	
		
		messageDispatcher.update();
	}
	
	public GameObject getGameObjectById(int id) 
	{
		if (gameObjects.containsKey(id)){
			return gameObjects.get(id);
		}
		else {
			return null;
		}
	}
	
	public void setObjectPosition(GameObject object, Vector3D position){
		object.setPosition(position);
	}
	
	public UnitObject getUnitObjectById(int id) 
	{
		if (gameObjects.containsKey(id)){
			GameObject gameObject = gameObjects.get(id);
			if (gameObject instanceof UnitObject) {
				return (UnitObject)gameObject;
			}
		}
		return null;
	}
	
	public UnitObject getPlayerUnitObject(int id) {
		if (gameObjects.containsKey(id)){
			GameObject gameObject = gameObjects.get(id);
			if (gameObject instanceof UnitObject) {
				UnitObject unitObject = (UnitObject)gameObject;
				if (unitObject.getAgent() != null) {
					return unitObject;
				}
			}
		}
		return null;
	}
	
	public UserAgent getUserAgent(int id) {
		if (gameObjects.containsKey(id)){
			GameObject gameObject = gameObjects.get(id);
			if (gameObject instanceof UnitObject) {
				UnitObject unitObject = (UnitObject)gameObject;
				if (unitObject.getAgent() != null) {
					return unitObject.getAgent();
				}
			}
		}
		return null;
	}
	
	/*
	 * 获取物体范围内的物体
	 */
	public LinkedList<DistanceObject<GameObject>> getObjectsInDistance(GameObject center, float distance)
	{
		float distanceSqr = distance * distance;
		LinkedList<DistanceObject<GameObject>> list = new LinkedList<DistanceObject<GameObject>>();
		for (Integer key : this.gameObjects.keySet()) {
			GameObject obj = this.gameObjects.get(key);
			float lenSqr = (float)center.getPosition().distanceSq(obj.getPosition());
			if (lenSqr <= distanceSqr) {
				list.add(new DistanceObject<GameObject>(obj, lenSqr));
			}
		}
		return list;
	}
	
	/*
	 * 获取物体范围内的单位
	 */
	public LinkedList<DistanceObject<UnitObject>> getUnitsInDistance(GameObject center, float distance)
	{
		float distanceSqr = distance * distance;
		LinkedList<DistanceObject<UnitObject>> list = new LinkedList<DistanceObject<UnitObject>>();
		for (Integer key : this.gameObjects.keySet()) {
			GameObject obj = this.gameObjects.get(key);
			if (obj instanceof UnitObject) {
				UnitObject unit = (UnitObject)obj;
				float lenSqr = (float)center.getPosition().distanceSq(unit.getPosition());
				if (lenSqr <= distanceSqr) {
					list.add(new DistanceObject<UnitObject>(unit, lenSqr));
				}
			}
		}
		return list;
	}
	
	public LinkedList<DistanceObject<UnitObject>> getAllUnits(GameObject center)
	{
		LinkedList<DistanceObject<UnitObject>> list = new LinkedList<DistanceObject<UnitObject>>();
		for (Integer key : this.gameObjects.keySet()) {
			GameObject obj = this.gameObjects.get(key);
			if (obj instanceof UnitObject) {
				UnitObject unit = (UnitObject)obj;
				list.add(new DistanceObject<UnitObject>(unit, 0f));
			}
		}
		return list;
	}
	
	/*
	 * 获取物体范围内的敌人
	 */
	public LinkedList<DistanceObject<UnitObject>> getEnemysInDistance(GameObject center, float distance) 
	{
		float distanceSqr = distance * distance;
		LinkedList<DistanceObject<UnitObject>> list = new LinkedList<DistanceObject<UnitObject>>();
		for (Integer key : this.gameObjects.keySet()) {
			GameObject obj = this.gameObjects.get(key);
			if (obj == center) {
				continue;
			}
			if (obj instanceof UnitObject) {
				UnitObject unit = (UnitObject)obj;
				if (!unit.isAlive()) {
					continue;
				}
				//同一个阵营的，不是敌人
				if (center.getGroup() == unit.getGroup()) {
					continue;
				}
				try {
					float lenSqr = (float)center.getPosition().distanceSq(unit.getPosition());
					if (lenSqr <= distanceSqr) {
						list.add(new DistanceObject<UnitObject>(unit, lenSqr));
					}
				}
				catch (Exception e){
					System.out.println("class is " + unit.getClass());
					e.printStackTrace();
				}
			}
		}
		return list;
	}
	
	public LinkedList<UnitObject> getAllUnitObject() {
		LinkedList<UnitObject> list = new LinkedList<UnitObject>();
		for (GameObject gameObject : gameObjects.values()) {
			if (gameObject instanceof UnitObject) {
				UnitObject unitObject = (UnitObject)gameObject;
				if (unitObject.isAlive()) {
					list.add(unitObject);
				}
			}
		}
		return list;
	}
}
